"""
第2章：请求体与数据验证 - 实战练习代码
配合 chapter02_request_body.md 讲义使用

运行方式：
cd /Users/baimu/PycharmProjects/fastApiProject3
uvicorn stage1_basics.chapter02_request_body_practice:app --reload --port 8003

访问：
- Swagger UI: http://127.0.0.1:8003/docs
- 根路径: http://127.0.0.1:8003/
"""

from fastapi import FastAPI, HTTPException, status
from pydantic import (
    BaseModel,
    Field,
    EmailStr,
    HttpUrl,
    validator,
    model_validator,
)
from typing import Optional, List, Dict, Any
from datetime import date, datetime
from enum import Enum

# ========== 应用初始化 ==========

app = FastAPI(
    title="第2章：请求体与数据验证练习",
    description="FastAPI 请求体与 Pydantic 模型的完整示例",
    version="1.0.0",
)

# ========== 1. 基础模型 ==========


class BasicUser(BaseModel):
    """基础用户模型"""

    name: str
    email: str
    age: int


@app.post("/demo/basic-user", tags=["基础模型"])
async def create_basic_user(user: BasicUser):
    """
    基础模型示例
    
    演示最简单的 Pydantic 模型使用
    """
    return {
        "message": "用户创建成功",
        "user": user,
        "note": "FastAPI 自动验证了数据类型",
    }


# ========== 2. 字段验证 ==========


class ValidatedUser(BaseModel):
    """带验证的用户模型"""

    username: str = Field(
        ..., min_length=3, max_length=20, description="用户名，3-20个字符", example="john_doe"
    )

    email: EmailStr = Field(..., description="电子邮箱", example="john@example.com")

    age: int = Field(
        ..., ge=0, le=120, description="年龄，0-120岁", example=25
    )

    bio: Optional[str] = Field(
        None, max_length=500, description="个人简介，最多500字符"
    )


@app.post("/demo/validated-user", tags=["字段验证"])
async def create_validated_user(user: ValidatedUser):
    """
    字段验证示例
    
    演示使用 Field() 进行各种验证：
    - 字符串长度限制
    - 数值范围限制
    - 邮箱格式验证
    - 可选字段
    """
    return {"message": "用户创建成功", "user": user}


# ========== 3. 可选字段与默认值 ==========


class UserProfile(BaseModel):
    """用户资料模型"""

    # 必需字段
    username: str = Field(..., min_length=3)
    email: EmailStr

    # 可选字段（可为 None）
    phone: Optional[str] = Field(None, pattern=r"^1[3-9]\d{9}$", description="手机号")
    website: Optional[HttpUrl] = None

    # 带默认值的字段
    age: int = Field(18, ge=0, le=120)
    role: str = Field("user", pattern="^(admin|user|guest)$")
    is_active: bool = True
    newsletter: bool = False

    class Config:
        json_schema_extra = {
            "example": {
                "username": "john_doe",
                "email": "john@example.com",
                "phone": "13800138000",
                "age": 25,
                "role": "user",
            }
        }


@app.post("/demo/user-profile", tags=["可选字段与默认值"])
async def create_user_profile(profile: UserProfile):
    """
    可选字段和默认值示例
    
    必需字段：username, email
    可选字段：phone, website
    带默认值：age(18), role(user), is_active(True), newsletter(False)
    """
    return {"message": "资料创建成功", "profile": profile}


# ========== 4. 嵌套模型 ==========


class Address(BaseModel):
    """地址模型"""

    street: str = Field(..., min_length=1, example="长安街1号")
    city: str = Field(..., min_length=1, example="北京")
    province: str = Field(..., min_length=1, example="北京市")
    country: str = Field(default="中国")
    zip_code: Optional[str] = Field(None, pattern=r"^\d{6}$", example="100000")


class ContactInfo(BaseModel):
    """联系信息模型"""

    email: EmailStr
    phone: str = Field(..., pattern=r"^1[3-9]\d{9}$")
    wechat: Optional[str] = None


class FullUser(BaseModel):
    """完整用户模型（包含嵌套）"""

    username: str = Field(..., min_length=3, max_length=20)
    address: Address
    contact: ContactInfo
    emergency_contact: Optional[ContactInfo] = None

    class Config:
        json_schema_extra = {
            "example": {
                "username": "john_doe",
                "address": {
                    "street": "长安街1号",
                    "city": "北京",
                    "province": "北京市",
                    "zip_code": "100000",
                },
                "contact": {
                    "email": "john@example.com",
                    "phone": "13800138000",
                    "wechat": "john_wechat",
                },
            }
        }


@app.post("/demo/full-user", tags=["嵌套模型"])
async def create_full_user(user: FullUser):
    """
    嵌套模型示例
    
    演示如何处理复杂的嵌套数据结构
    """
    return {"message": "用户创建成功", "user": user}


# ========== 5. 列表嵌套 ==========


class Tag(BaseModel):
    """标签模型"""

    name: str = Field(..., min_length=1, max_length=20)
    color: str = Field(default="blue", pattern="^(blue|green|red|yellow|purple)$")


class BlogPost(BaseModel):
    """博客文章模型"""

    title: str = Field(..., min_length=1, max_length=200)
    content: str = Field(..., min_length=10)
    tags: List[Tag] = Field(default_factory=list, max_items=10)
    keywords: List[str] = Field(default_factory=list, max_items=10)

    class Config:
        json_schema_extra = {
            "example": {
                "title": "FastAPI 入门教程",
                "content": "这是一篇详细的 FastAPI 教程，包含了...",
                "tags": [
                    {"name": "python", "color": "blue"},
                    {"name": "fastapi", "color": "green"},
                ],
                "keywords": ["python", "fastapi", "web", "api"],
            }
        }


@app.post("/demo/blog-post", tags=["列表嵌套"])
async def create_blog_post(post: BlogPost):
    """
    列表嵌套示例
    
    演示如何处理列表类型的嵌套模型
    """
    return {
        "message": "文章创建成功",
        "post": post,
        "tag_count": len(post.tags),
        "keyword_count": len(post.keywords),
    }


# ========== 6. 自定义验证器 ==========


class UserRegistration(BaseModel):
    """用户注册模型（带自定义验证）"""

    username: str = Field(..., min_length=3, max_length=20)
    email: EmailStr
    password: str = Field(..., min_length=8, max_length=100)
    confirm_password: str
    age: int = Field(..., ge=18, le=120)
    terms_accepted: bool

    @validator("username")
    def username_alphanumeric(cls, v):
        """验证用户名只包含字母、数字和下划线"""
        if not v.replace("_", "").isalnum():
            raise ValueError("用户名只能包含字母、数字和下划线")
        return v

    @validator("password")
    def password_strength(cls, v):
        """验证密码强度"""
        if not any(c.isupper() for c in v):
            raise ValueError("密码必须包含至少一个大写字母")
        if not any(c.islower() for c in v):
            raise ValueError("密码必须包含至少一个小写字母")
        if not any(c.isdigit() for c in v):
            raise ValueError("密码必须包含至少一个数字")
        return v

    @validator("confirm_password")
    def passwords_match(cls, v, values):
        """验证两次密码输入一致"""
        if "password" in values and v != values["password"]:
            raise ValueError("两次密码输入不一致")
        return v

    @validator("terms_accepted")
    def must_accept_terms(cls, v):
        """必须同意服务条款"""
        if not v:
            raise ValueError("必须同意服务条款才能注册")
        return v

    class Config:
        json_schema_extra = {
            "example": {
                "username": "john_doe",
                "email": "john@example.com",
                "password": "SecurePass123",
                "confirm_password": "SecurePass123",
                "age": 25,
                "terms_accepted": True,
            }
        }


@app.post("/demo/register", tags=["自定义验证器"])
async def register_user(user: UserRegistration):
    """
    自定义验证器示例
    
    演示如何使用 @validator 装饰器进行复杂验证：
    - 用户名格式验证
    - 密码强度验证
    - 两次密码一致性验证
    - 必须同意条款验证
    """
    # 实际应用中应该加密密码并保存到数据库
    return {
        "message": "注册成功",
        "username": user.username,
        "email": user.email,
    }


# ========== 7. Root 验证器 ==========


class DateRange(BaseModel):
    """日期范围模型"""

    start_date: date = Field(..., description="开始日期")
    end_date: date = Field(..., description="结束日期")

    @model_validator(mode='after')
    def validate_date_range(self):
        """验证日期范围的有效性"""
        if self.start_date and self.end_date:
            if self.end_date < self.start_date:
                raise ValueError("结束日期必须在开始日期之后")

            # 验证日期范围不超过1年
            days_diff = (self.end_date - self.start_date).days
            if days_diff > 365:
                raise ValueError("日期范围不能超过1年")

        return self

    class Config:
        json_schema_extra = {
            "example": {"start_date": "2024-01-01", "end_date": "2024-12-31"}
        }


@app.post("/demo/date-range", tags=["Root验证器"])
async def create_date_range(date_range: DateRange):
    """
    模型验证器示例
    
    演示如何使用 @model_validator 进行跨字段验证
    """
    days = (date_range.end_date - date_range.start_date).days
    return {
        "message": "日期范围有效",
        "date_range": date_range,
        "total_days": days,
    }


# ========== 8. 枚举类型 ==========


class UserRole(str, Enum):
    """用户角色枚举"""

    ADMIN = "admin"
    USER = "user"
    GUEST = "guest"


class Priority(int, Enum):
    """优先级枚举"""

    LOW = 1
    MEDIUM = 2
    HIGH = 3
    URGENT = 4


class Task(BaseModel):
    """任务模型"""

    title: str = Field(..., min_length=1, max_length=200)
    description: Optional[str] = Field(None, max_length=1000)
    priority: Priority = Priority.MEDIUM
    assigned_to_role: UserRole
    due_date: Optional[date] = None

    class Config:
        json_schema_extra = {
            "example": {
                "title": "完成 FastAPI 教程",
                "description": "学习并完成所有章节",
                "priority": 3,
                "assigned_to_role": "user",
                "due_date": "2024-12-31",
            }
        }


@app.post("/demo/task", tags=["枚举类型"])
async def create_task(task: Task):
    """
    枚举类型示例
    
    演示如何使用 Enum 限制字段的可选值
    """
    return {
        "message": "任务创建成功",
        "task": task,
        "priority_name": task.priority.name,
        "priority_value": task.priority.value,
    }


# ========== 9. 复杂数据类型 ==========


class Statistics(BaseModel):
    """统计数据模型"""

    # 字典：字符串到整数
    scores: Dict[str, int] = Field(default_factory=dict)

    # 字典：字符串到字符串列表
    categories: Dict[str, List[str]] = Field(default_factory=dict)

    # 任意 JSON
    metadata: Dict[str, Any] = Field(default_factory=dict)

    class Config:
        json_schema_extra = {
            "example": {
                "scores": {"math": 90, "english": 85, "science": 92},
                "categories": {
                    "hobbies": ["reading", "coding", "sports"],
                    "skills": ["python", "fastapi", "docker"],
                },
                "metadata": {
                    "created_by": "system",
                    "version": 1,
                    "tags": ["test", "demo"],
                },
            }
        }


@app.post("/demo/statistics", tags=["复杂数据类型"])
async def create_statistics(stats: Statistics):
    """
    复杂数据类型示例
    
    演示如何处理字典和任意 JSON 数据
    """
    return {
        "message": "统计数据创建成功",
        "stats": stats,
        "total_scores": sum(stats.scores.values()) if stats.scores else 0,
    }


# ========== 10. 实战示例：电商订单系统 ==========


class ProductInfo(BaseModel):
    """商品信息"""

    product_id: int = Field(..., ge=1)
    name: str = Field(..., min_length=1, max_length=200)
    price: float = Field(..., gt=0)
    quantity: int = Field(..., ge=1)

    @validator("price")
    def price_precision(cls, v):
        """价格保留两位小数"""
        return round(v, 2)


class ShippingAddress(BaseModel):
    """收货地址"""

    recipient: str = Field(..., min_length=2, max_length=50)
    phone: str = Field(..., pattern=r"^1[3-9]\d{9}$")
    province: str
    city: str
    district: str
    street: str = Field(..., min_length=5)
    zip_code: Optional[str] = Field(None, pattern=r"^\d{6}$")


class PaymentMethod(str, Enum):
    """支付方式"""

    ALIPAY = "alipay"
    WECHAT = "wechat"
    CARD = "card"
    COD = "cod"  # 货到付款


class Order(BaseModel):
    """订单模型"""

    order_id: Optional[str] = None
    items: List[ProductInfo] = Field(..., min_items=1, max_items=50)
    shipping_address: ShippingAddress
    payment_method: PaymentMethod
    coupon_code: Optional[str] = Field(None, min_length=4, max_length=20)
    note: Optional[str] = Field(None, max_length=500)
    total_amount: Optional[float] = None

    @model_validator(mode='after')
    def calculate_total(self):
        """自动计算订单总额"""
        if self.items:
            total = sum(item.price * item.quantity for item in self.items)
            self.total_amount = round(total, 2)
        return self

    class Config:
        json_schema_extra = {
            "example": {
                "items": [
                    {
                        "product_id": 1,
                        "name": "笔记本电脑",
                        "price": 5999.0,
                        "quantity": 1,
                    },
                    {"product_id": 2, "name": "鼠标", "price": 99.0, "quantity": 2},
                ],
                "shipping_address": {
                    "recipient": "张三",
                    "phone": "13800138000",
                    "province": "北京市",
                    "city": "北京市",
                    "district": "朝阳区",
                    "street": "某某街道123号",
                    "zip_code": "100000",
                },
                "payment_method": "alipay",
                "coupon_code": "SAVE10",
                "note": "请尽快发货",
            }
        }


# 模拟订单数据库
orders_db: List[Order] = []


@app.post("/api/orders", tags=["电商订单系统"], status_code=status.HTTP_201_CREATED)
async def create_order(order: Order):
    """
    创建订单
    
    完整的电商订单示例，包含：
    - 商品列表验证
    - 收货地址验证
    - 支付方式选择
    - 自动计算总额
    """
    # 生成订单号
    order.order_id = f"ORD{datetime.now().strftime('%Y%m%d%H%M%S')}"

    # 保存到"数据库"
    orders_db.append(order)

    return {
        "message": "订单创建成功",
        "order_id": order.order_id,
        "total_amount": order.total_amount,
        "items_count": len(order.items),
    }


@app.get("/api/orders", tags=["电商订单系统"])
async def list_orders():
    """获取所有订单"""
    return {"total": len(orders_db), "orders": orders_db}


# ========== 11. 实战示例：社交媒体帖子 ==========


class Author(BaseModel):
    """作者信息"""

    user_id: int = Field(..., ge=1)
    username: str = Field(..., min_length=3, max_length=20)
    avatar: Optional[HttpUrl] = None


class MediaType(str, Enum):
    """媒体类型"""

    IMAGE = "image"
    VIDEO = "video"
    AUDIO = "audio"


class Media(BaseModel):
    """媒体文件"""

    type: MediaType
    url: HttpUrl
    thumbnail: Optional[HttpUrl] = None
    duration: Optional[int] = Field(None, ge=0, description="时长（秒）")

    @validator("duration")
    def duration_required_for_video(cls, v, values):
        """视频和音频必须提供时长"""
        media_type = values.get("type")
        if media_type in [MediaType.VIDEO, MediaType.AUDIO] and v is None:
            raise ValueError(f"{media_type.value} 类型必须提供时长")
        return v


class PostCreate(BaseModel):
    """帖子创建模型"""

    author: Author
    content: str = Field(..., min_length=1, max_length=5000)
    media: List[Media] = Field(default_factory=list, max_items=9)
    tags: List[str] = Field(default_factory=list, max_items=10)
    location: Optional[str] = Field(None, max_length=100)
    is_public: bool = True

    @validator("content")
    def content_not_empty(cls, v):
        """内容不能只有空格"""
        if not v.strip():
            raise ValueError("内容不能为空")
        return v.strip()

    @validator("tags")
    def validate_tags(cls, v):
        """验证标签格式"""
        for tag in v:
            if not tag.strip():
                raise ValueError("标签不能为空")
            if len(tag) > 20:
                raise ValueError("单个标签不能超过20个字符")
        return [tag.strip() for tag in v]

    class Config:
        json_schema_extra = {
            "example": {
                "author": {
                    "user_id": 1,
                    "username": "john_doe",
                    "avatar": "https://example.com/avatar.jpg",
                },
                "content": "今天学习了 FastAPI，感觉很棒！#学习笔记",
                "media": [
                    {
                        "type": "image",
                        "url": "https://example.com/photo1.jpg",
                        "thumbnail": "https://example.com/photo1_thumb.jpg",
                    }
                ],
                "tags": ["FastAPI", "Python", "学习"],
                "location": "北京",
                "is_public": True,
            }
        }


# 模拟帖子数据库
posts_db: List[dict] = []


@app.post("/api/posts", tags=["社交媒体"], status_code=status.HTTP_201_CREATED)
async def create_post(post: PostCreate):
    """
    创建帖子
    
    社交媒体帖子示例，包含：
    - 作者信息
    - 文本内容
    - 媒体文件列表（图片、视频、音频）
    - 标签
    - 位置信息
    """
    post_data = {
        "post_id": len(posts_db) + 1,
        "created_at": datetime.now().isoformat(),
        **post.dict(),
    }

    posts_db.append(post_data)

    return {
        "message": "帖子创建成功",
        "post_id": post_data["post_id"],
        "media_count": len(post.media),
        "tags": post.tags,
    }


@app.get("/api/posts", tags=["社交媒体"])
async def list_posts():
    """获取所有帖子"""
    return {"total": len(posts_db), "posts": posts_db}


# ========== 根路径 ==========


@app.get("/", tags=["根路径"])
async def root():
    """
    根路径
    
    返回欢迎信息和可用端点列表
    """
    return {
        "message": "欢迎来到第2章：请求体与数据验证练习！",
        "features": [
            "基础模型",
            "字段验证",
            "嵌套模型",
            "列表嵌套",
            "自定义验证器",
            "枚举类型",
            "复杂数据类型",
        ],
        "examples": {
            "电商订单系统": "/api/orders",
            "社交媒体帖子": "/api/posts",
        },
        "docs": "/docs",
    }


# ========== 启动信息 ==========

if __name__ == "__main__":
    print("=" * 70)
    print("📚 第2章：请求体与数据验证 - 实战练习")
    print("=" * 70)
    print("\n启动命令:")
    print(
        "uvicorn stage1_basics.chapter02_request_body_practice:app --reload --port 8003\n"
    )
    print("访问地址:")
    print("- Swagger UI: http://127.0.0.1:8003/docs")
    print("- ReDoc: http://127.0.0.1:8003/redoc")
    print("- 根路径: http://127.0.0.1:8003/")
    print("\n学习重点:")
    print("1. 理解 Pydantic 模型的作用")
    print("2. 掌握 Field() 的各种验证参数")
    print("3. 学会处理嵌套模型")
    print("4. 编写自定义验证器")
    print("5. 实战：电商订单系统、社交媒体帖子")
    print("\n" + "=" * 70)

